package com.tedu.udm.shiro;

import com.alibaba.fastjson.JSON;
import com.tedu.udm.mapper.UserMapper;
import com.tedu.udm.pojo.entity.User;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.util.*;

/**
 * 自定义Realm，用来实现用户的认证和授权
 * AuthenticatingRealm 只负责认证（登录）的Realm父类
 */
@Slf4j
public class JWTAuthenticatingRealm extends AuthorizingRealm {

    @Value("${udm.jwt.secret-key}")
    private String secretKey;

    @Autowired
    UserMapper userMapper;
    /**
     * Shiro的认证方法我们需要在这个方法中来获取用户的信息（从数据库中）
     * @param token   用户登录时的Token（令牌），这个对象中将存放着我们用户在浏览器中存放的账号和密码
     * @return 返回一个AuthenticationInfo 对象，这个返回以后Shiro会调用这个对象中的一些方法来完成对密码的验证 密码是由Shiro进行验证是否合法
     * @throws AuthenticationException   如果认证失败，Shiro就会抛出AuthenticationException 也可以手动抛出这个AuthenticationException
     *      以及它的任意子异常类，不同的异常类型对应认证过程中的不同错误情况，我们需要根据异常类型来为用户返回特定的响应数据
     * AuthenticationException  异常的子类  可以自己抛出
     *      AccountException  账号异常  可以自己抛出
     *      UnknownAccountException  账号不存在的异常  可以自己抛出
     *      LockedAccountException   账号异常锁定异常  可以自己抛出
     *      IncorrectCredentialsException  密码错误异常（这个异常会在Shiro进行密码验证时抛出）
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTAuthenticationToken;
    }

    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        JWTCredentialsMatcher jwtCredentialsMatcher = new JWTCredentialsMatcher();
        super.setCredentialsMatcher(jwtCredentialsMatcher);
    }

    // 检验权限时调用
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.debug("经过权限认证！");
        String jwt = principalCollection.toString();
        Claims claims = Jwts.parser()
                            .setSigningKey(secretKey)
                            .parseClaimsJws(jwt)
                            .getBody();
        // 获取JWT中的权限信息
        String authoritiesJsonString = claims.get("authoritiesJsonString", String.class);
        List<Permission> list = JSON.parseArray(authoritiesJsonString, Permission.class);
        log.debug("从JWT中获取authoritiesJsonString：{}", authoritiesJsonString);
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> psermissions = new HashSet<>();
        for ( Permission authority:list) {
            psermissions.add(authority.getAuthority());
        }
        info.addStringPermissions(psermissions);
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String token = (String) authenticationToken.getCredentials();
        log.debug("经过登录认证！");
        /*
        //将AuthenticationToken强转成UsernamePasswordToken 这样获取账号和密码更加的方便
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        //获取用户在浏览器中输入的账号
        String username = token.getUsername();
        //认证账号,正常情况我们需要这里从数据库中获取账号的信息，以及其他关键数据，例如账号是否被冻结等等
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username",username);
        User user = userMapper.selectOne(wrapper);

        if (user == null) {//判断用户账号是否正确
            throw new ServiceException(ServiceCode.ERR_UNAUTHORIZED_DISABLED,"登录失败，用户名或密码错！");
        }
        */
        /**
         * 创建密码认证对象，由Shiro自动认证密码
         * 参数1  数据库中的账号（页面账号也可）
         * 参数2  数据库中的密码
         * 参数3  当前Relam的名字
         * 如果密码认证成功，会返回一个用户身份对象；如果密码验证失败则抛出异常
         */

        //认证密码是否正确
        return new SimpleAuthenticationInfo(token,token, getName());
    }
}